using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Random = UnityEngine.Random;

public enum DigMethod : int
{
    Random,
    Sequence,
    Interval,
    Snake
}

public class GridGenerator
{
    public static readonly int GRID_SIZE = 9;
    static readonly int K_DEFALULT = 11;

    private int m_RemainNumberLSL;
    private int m_RemainNumberUSL;

    private int[] m_grid;
    private int[] m_answerGrid;

    public DigMethod DigMethod;
    // range [22, 80/DigNumberUSL]
    public int RemainNumberLSL
    {
        get => m_RemainNumberLSL;
        set
        {
            if (value > m_RemainNumberUSL)
                m_RemainNumberLSL = m_RemainNumberUSL;
            else if (value < 22)
                m_RemainNumberLSL = 22;
            else if (value > 80)
                m_RemainNumberLSL = 80;
            else
                m_RemainNumberLSL = value;

        }
    }
    // range [22/DigNumberUSL, 80]
    public int RemainNumberUSL
    {
        get => m_RemainNumberUSL;
        set
        {
            if (value < m_RemainNumberLSL)
                m_RemainNumberUSL = m_RemainNumberLSL;
            else if (value < 22)
                m_RemainNumberUSL = 22;
            else if (value > 80)
                m_RemainNumberUSL = 80;
            else
                m_RemainNumberUSL = value;

        }
    }

    public int[] Grid => m_grid;
    public int[] AnswerGrid => m_answerGrid;

    public GridGenerator()
    {
        m_RemainNumberLSL = 22;
        m_RemainNumberUSL = 30;
        DigMethod = DigMethod.Sequence;
    }

    public int[] GenerateGrid()
    {
        m_answerGrid = GenerateAnswerGrid(K_DEFALULT);
        m_grid = GeneratePuzzleGrid((int[])m_answerGrid.Clone());

        return m_grid;
    }

    int[] GenerateAnswerGrid(int K)
    {
        void Initialize(int[] a, int[][] w)
        {
            for (int i = 0; i < a.Length; i++)
            {
                a[i] = 0;
            }

            for (int i = 0; i < w.Length; i++)
            {
                for (int j = 0; j < w[i].Length; j++)
                {
                    w[i][j] = 0;
                }
            }
        }

        int[] a = new int[81];
        int[][] w = new int[3][]
        {
            new int[GRID_SIZE],
            new int[GRID_SIZE],
            new int[GRID_SIZE]
        };

        int[] countNumber;

        while (true)
        {
            // (1) initialize data
            Initialize(a, w);
            countNumber = new int[9];
            Random.InitState((int)(System.DateTime.Now.Ticks / 10000000));
            for (int count = 0; count < K; count++)
            {
                // (2) random number lower 81
                int n = Random.Range(0, 81);
                // (3) random number between 1 to 9
                int t = Random.Range(1, 10);
                if (countNumber[t - 1] >= 9)
                {
                    count--;
                    continue;
                }
                int b = 1 << (t - 1);
                // (4) judge do a[n] have number
                // (5) judge if can place number
                while (a[n] != 0 || !GridSolver.CanPlaceNumber(a, w, n, b))
                {
                    n = (n + 1) % 81;
                }
                // (6) place number
                a[n] = t;
                countNumber[t - 1]++;
                w[0][n / GRID_SIZE] |= b;
                w[1][n % GRID_SIZE] |= b;
                w[2][n / GRID_SIZE / 3 * 3 + n % GRID_SIZE / 3] |= b;
            }
            // (7) solving map
            if (GridSolver.SolvingMap(a))
            {
                break;
            }
        }

        return a;
    }

    int[] GeneratePuzzleGrid(int[] answerGrid)
    {
        int digNumber = Random.Range(81- RemainNumberUSL, 81 - RemainNumberLSL);
        // Sequence dig number
        switch(DigMethod)
        {
            case DigMethod.Random:
            case DigMethod.Interval:
                Debug.Log("Interval");
                DigNumberSnake(digNumber, answerGrid, true);
                break;
            case DigMethod.Snake:
                Debug.Log("Snake");
                DigNumberSnake(digNumber, answerGrid, false);
                break;
            case DigMethod.Sequence:
                Debug.Log("Sequence");
                DigNumberSequence(digNumber, answerGrid);
                break;
        }
        return answerGrid;
    }

    #region Dig number method

    void DigNumberSnake(int digNumber, int[] answerGrid, bool isInterval)
    {
        Iterator iterator = new Iterator(0, GRID_SIZE - 1);
        for (int i = 0; i < GRID_SIZE; i++)
        {
            iterator.Action = j =>
            {
                if (digNumber == 0)
                    return;
                if (CheckCanDig(answerGrid, i * GRID_SIZE + j))
                {
                    answerGrid[i * GRID_SIZE + j] = 0;
                    digNumber--;
                }
            };
            int start;
            if (i % 2 == 0)
            {
                iterator.Shift = x =>
                {
                    if (isInterval)
                        x += 2;
                    else
                        x++;
                    return x;
                };
                start = 0;
            }
            else
            {
                iterator.Shift = x =>
                {
                    if (isInterval)
                        x -= 2;
                    else
                        x--;
                    return x;
                };
                if (isInterval)
                    start = GRID_SIZE - 2;
                else
                    start = GRID_SIZE - 1;
            }
            iterator.Foreach(start);
        }
    }

    void DigNumberSequence(int digNumber, int[] answerGrid)
    {
        for (int i = 0; i < GRID_SIZE; i++)
        {
            for (int j = 0; j < GRID_SIZE; j++)
            {
                if (digNumber == 0)
                    return;
                if (CheckCanDig(answerGrid, i * GRID_SIZE + j))
                {
                    answerGrid[i * GRID_SIZE + j] = 0;
                    digNumber--;
                }
            }
        }
    }

    bool CheckCanDig(int[] grid, int index)
    {
        if (grid[index] == 0)
            return false;

        int beforeNumber = grid[index];
        int[][] w = GridSolver.GetWeightArray(grid);
        for (int i = 1; i <= 9; i++)
        {
            grid[index] = 0;
            if (beforeNumber != i && GridSolver.CanPlaceNumber(grid, w, index, 1 << (i - 1)))
            {
                grid[index] = i;
                if (GridSolver.SolvingMap((int[])grid.Clone()))
                {
                    grid[index] = beforeNumber;
                    return false;
                }
            }
        }
        return true;
    }
    #endregion

#if UNITY_EDITOR
    public void PrintGrid(int[] grid, int row, int col)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                if ((j + 1) % 3 == 0)
                    sb.Append($"{grid[i * row + j]}|");
                else
                    sb.Append($"{grid[i * row + j]} ");
            }
            if ((i + 1) % 3 == 0)
                sb.Append("\n------------------\n");
            else
                sb.Append("\n");
        }
        Debug.Log(sb.ToString());
    }
#endif
}
